import Designer from '@/index';
import EventEmitter from '../eventEmitter';
import Canvas from '../canvas';
import Transformer from '../transformer';
import { getImage } from '../image';
import {
  getOffsetXY,
  getNodesMinEnclosingRect,
  getType,
  nodesTreeToList,
  draw,
  throttle,
  nodeMerge,
  colorToRGBA,
  diff,
  nodesToMap,
  log,
  isDef,
  uuid,
  getBezierCurveControlPoints,
  getBezierCurvePath,
  cloneDeep,
} from '@/utils';
import init from './init';
import { hotkeyAction, handleHover, updateCursor } from './handle';
import { drawOutline, drawBg, drawScrollbar, drawGuides, drawEdges } from './draw';
import { gridConfig, edgeTypes, edgeConfig } from './constant';

/**
 * 画布类
 */
export default class Stage extends EventEmitter {
  className = 'Stage'; // 类名
  containerEl = null; // container 元素
  designerEl = null; // designer 元素
  canvas = null; // canvas 元素
  ctx = null; // canvas 上下文
  textareaEl = document.createElement('textarea'); // 文本编辑元素
  abortController = new AbortController(); // 事件废弃控制器
  transformer = new Transformer(this); // 转换器
  offset = { x: 0, y: 0 }; // 记录偏移量
  lastOffset = { x: 0, y: 0 }; // 记录上一次偏移量
  scale = 1; // 缩放比例
  lastScale = 1; // 记录上一次的缩放值
  offsetCoord = { x: 0, y: 0 }; // 记录鼠标相对画布坐标（画布相对坐标）
  dragAnchor = { x: 0, y: 0 }; // 记录拖动时的锚点（画布相对坐标）
  scaleAnchor = { x: 0, y: 0 }; // 记录缩放时的锚点（画布相对坐标）
  scrollAnchor = { x: 0, y: 0 }; // 记录滚动条操作时的锚点（画布相对坐标）
  nodeDragAnchor = { x: 0, y: 0 }; // 记录节点拖动时的锚点（画布网格坐标）
  regionAnchor = { x: 0, y: 0 }; // 记录框选时的锚点（画布网格坐标）
  guides = []; // 辅助线
  edges = []; // 节点连线
  dragging = false; // 画布拖动标识
  scrolling = false; // 滚动条操作标识
  nodeDragging = false; // 节点拖动标识
  regioning = false; // 框选标识
  edging = false; // 节点连线标识
  selectedNodes = []; // 已选节点
  selectedEdges = []; // 已选节点连线
  hoveringNode = null; // 当前 hover 节点
  depthOperation = false; // 是否可深度操作节点
  lastTopGroup = null; // 记录上一次最顶层父级编组
  editTextNode = null; // 记录当前编辑的文本节点
  tempAnchor = null; // 临时锚点
  hoveringAnchor = null; // 当前 hover 锚点
  hoveringEdge = null; // 当前 hover 节点连线
  hoveringScrollbar = null; // 当前 hover 滚动条
  nodes = []; // 子节点
  history = []; // 历史状态
  historyStep = 0; // 历史状态数据索引
  pressedKeys = new Set(); // 记录已按下的键值
  // 动画帧信息 time: 动画执行的时间, timeDiff: 上一帧与当前帧的时间差, lastTime: 上一帧的时间, frameRate: 帧率
  frame = { time: 0, timeDiff: 0, lastTime: 0, frameRate: 0 };
  constructor(config = {}) {
    super();
    // container 元素选择器或 dom 元素
    this.container = config.container;
    // 创建 canvas 对象
    this.canvas = new Canvas({ width: config.width, height: config.height });
    // 每秒帧数（绘制函数每秒调用最大次数）
    this.fps = config.fps ?? 144;
    // 是否根据 container 元素自动设置宽高
    this.autoSize = config.autoSize ?? true;
    // 缩放比率
    this.scaleRatio = config.scaleRatio ?? 1.1;
    // 最大缩放比例
    this.maxScale = config.maxScale ?? 256;
    // 最小缩放比例
    this.minScale = config.minScale ?? 0.02;
    // 内边距（控制视口）
    this.padding = config.padding ?? null;
    // 画布是否有滚动条
    this.scrollbar = config.scrollbar ?? true;
    // 画布是否启用事件
    this.eventEnabled = config.eventEnabled ?? true;
    // 画布是否可拖动
    this.draggable = config.draggable ?? true;
    // 画布是否可缩放
    this.scaleable = config.scaleable ?? true;
    // 是否可选择节点
    this.selectable = config.selectable ?? true;
    // 是否可框选节点
    this.regionable = config.regionable ?? true;
    // 是否可转换节点
    this.transformable = config.transformable ?? true;
    // 是否可堆叠节点
    this.stackable = config.stackable ?? true;
    // 是否开启内置历史状态记录（撤销，回退）
    this.recordable = config.recordable ?? true;
    // 是否开启内置辅助线功能
    this.guidesable = config.guidesable ?? true;
    // 是否开启节点连线功能
    this.edgeable = config.edgeable ?? true;
    // 设置画布网格
    this.setGrid(config.grid ?? false);
    // 设置节点连线默认配置
    this.setEdgeConfig(config.edgeConfig ?? edgeConfig);
    // 是否开启开发者模式
    this.devMode = config.devMode ?? false;
    // 获取 container 元素
    this.containerEl = getType(this.container) === 'String' ? document.querySelector(this.container) : this.container;
    if (!this.containerEl) throw new Error('未指定或未找到 container 元素');
    // container 元素中已有 stage 实例，阻断当前 stage 实例初始化
    if (this.containerEl.stage) return;
    // 挂载 stage 实例到 container 元素中（保证container 元素中的 stage 对象单例）
    this.containerEl.stage = this;
    // 初始化
    init.call(this);
  }
  // 事件
  onWindowMousemove;
  onWindowDragover;
  onWindowMouseup;
  onWindowKeydown;
  onWindowKeyup;
  onCopy;
  onCut;
  onPaste;
  onMousemove;
  onMousedown;
  onMouseup;
  onDblclick;
  onContextmenu;
  onWheel;
  onKeydown;
  onKeyup;
  onScrollbar;
  onScrollbarEnd;
  onStageDrag;
  onStageDragEnd;
  onNodeDrag;
  onNodeDragEnd;
  onRegion;
  onRegionEnd;
  onEdge;
  onEdgeEnd;
  // 画布 CSS 宽度
  get width() {
    return this.canvas?.width;
  }
  // 画布 CSS 高度
  get height() {
    return this.canvas?.height;
  }
  // 画布 CSS 宽度同步
  set width(val) {
    if (this.canvas) this.canvas.width = val;
  }
  // 画布 CSS 高度同步
  set height(val) {
    if (this.canvas) this.canvas.height = val;
  }
  // 画布水平像素数
  get canvasWidth() {
    return this.canvas?.el.width;
  }
  // 画布垂直像素数
  get canvasHeight() {
    return this.canvas?.el.height;
  }
  // 画布像素比（画布像素 与 CSS 像素之比）
  get pixelRatio() {
    return this.canvas?.pixelRatio || devicePixelRatio;
  }
  // 画布视口信息
  get viewport() {
    const { padding, canvasWidth, canvasHeight, pixelRatio } = this;
    // 计算视口外边距
    const m = (p) => +p * pixelRatio || 0;
    const margin = { top: m(padding?.[0]), right: m(padding?.[1]), bottom: m(padding?.[2]), left: m(padding?.[3]) };
    const x = margin.left;
    const y = margin.top;
    const width = canvasWidth - margin.left - margin.right || canvasWidth;
    const height = canvasHeight - margin.top - margin.bottom || canvasHeight;
    return {
      x, // 视口 x 偏移
      y, // 视口 y 偏移
      width, // 视口宽度
      height, // 视口高度
      center: { x: x + width / 2, y: y + height / 2 }, // 视口中心点
      aspectRatio: width / height, // 视口宽高比
      margin, // 视口外边距
    };
  }
  // 获取子节点的映射对象
  get nodesMap() {
    return nodesToMap(this.nodes);
  }
  // 可见子节点
  get visibleNodes() {
    return this.nodes.filter((i) => i.visible);
  }
  // 获取画布实际绘制区域最小外接矩形
  get minEnclosingRect() {
    return getNodesMinEnclosingRect(this.visibleNodes);
  }
  // 获取画布实际绘制区域中心点坐标（画布相对坐标）
  get realCenter() {
    const { minEnclosingRect, width, height } = this;
    // 不存在画布实际绘制区域 返回画布中心点
    if (!minEnclosingRect) return this.getOffsetCoord({ x: width / 2, y: height / 2 });
    return this.getOffsetCoord({ x: minEnclosingRect.centerX, y: minEnclosingRect.centerY });
  }
  // 溢出的画布相对坐标
  get overflowCoord() {
    const { offsetCoord, canvasWidth, canvasHeight } = this;
    let x = 0;
    let y = 0;
    if (offsetCoord.x < 0) x = offsetCoord.x;
    else if (offsetCoord.x > canvasWidth) x = offsetCoord.x - canvasWidth;
    if (offsetCoord.y < 0) y = offsetCoord.y;
    else if (offsetCoord.y > canvasHeight) y = offsetCoord.y - canvasHeight;
    return {
      x,
      y,
      isOverflow: x || y, // 是否溢出
      top: offsetCoord.y, // 上边距离
      bottom: canvasHeight - offsetCoord.y, // 下边距离
      left: offsetCoord.x, // 上边距离
      right: canvasWidth - offsetCoord.x, // 右边距离
    };
  }
  // 画布网格坐标
  get gridCoord() {
    return this.getGridCoord(this.offsetCoord);
  }
  // 画布绝对坐标
  get absoluteCoord() {
    return this.getAbsoluteCoord(this.offsetCoord);
  }
  // 相对于浏览器窗口的坐标
  get fixedCoord() {
    return this.getFixedCoord(this.absoluteCoord);
  }
  // 当前鼠标悬停位置的节点
  get hoverNode() {
    return this.visibleNodes.findLast((node) => node.isPointIn()) || this;
  }
  // 当前可拖动的节点
  get draggableNodes() {
    const { selectedNodes, depthOperation } = this;
    // 过滤出 可见 且 未锁定节点
    const draggableNodes = selectedNodes.filter((node) => node.visible && !node.locked);
    // 实际可拖动的节点 = 可深度操作节点 ? 当前节点 : 最顶层父级编组
    draggableNodes.forEach((node, index, array) => {
      if (depthOperation) array[index] = node;
      else array[index] = node.topGroup ?? node;
    });
    return [...new Set(draggableNodes)].filter((node) => !node.locked);
  }
  // 获取所有子节点
  get allNodes() {
    return nodesTreeToList(this.nodes);
  }
  // 获取所有可见子节点
  get allVisibleNodes() {
    return this.allNodes.filter((i) => i.visible);
  }
  // 获取所有子节点的映射对象
  get allNodesMap() {
    return nodesToMap(this.allNodes);
  }
  // 获取所有子节点的锚点
  get allAnchors() {
    return this.allNodes.reduce((arr, node) => (arr.push(...(node._anchors || [])), arr), []);
  }
  // 获取所有可见子节点的锚点
  get allVisibleNodesAnchors() {
    return this.allVisibleNodes.reduce((arr, node) => (arr.push(...(node._anchors || [])), arr), []);
  }
  // 获取所有受影响的节点连线
  get allEffectEdges() {
    const { selectedEdges, selectedNodes, edges } = this;
    const effectEdges = new Set();
    const edgesMap = edges.reduce((map, edge) => ((map[edge.id] = edge), map), {});
    // 已选节点连线
    selectedEdges.forEach((id) => effectEdges.add(edgesMap[id]));
    // 已选节点关联的节点连线
    selectedNodes.forEach((node) => {
      edges.filter((i) => i.source === node.id || i.target === node.id).forEach((edge) => effectEdges.add(edge));
    });
    return [...effectEdges].filter(Boolean);
  }
  // 私有节点连线集
  get _edges() {
    const { edges, allNodesMap, edgeConfig, selectedEdges, hoveringEdge, allEffectEdges, gridCoord } = this;
    const ctx = Stage.canvas.getContext();
    // 节点锚点集的映射对象 Map
    const nodeAnchorsMap = new Map();
    return edges.reduce((arr, edge) => {
      const { id, source, sourceAnchor, target, targetAnchor } = edge;
      const sourceNode = allNodesMap[source];
      const targetNode = allNodesMap[target];
      // 性能优化 避免重复获取锚点集的映射对象
      if (!nodeAnchorsMap.has(source)) nodeAnchorsMap.set(source, sourceNode?.anchorsMap);
      if (!nodeAnchorsMap.has(target)) nodeAnchorsMap.set(target, targetNode?.anchorsMap);
      const _sourceAnchor = nodeAnchorsMap.get(source)?.[sourceAnchor];
      const _targetAnchor = nodeAnchorsMap.get(target)?.[targetAnchor];
      const start = _sourceAnchor?.p;
      const end = _targetAnchor?.p;
      const _edge = {
        ...edgeConfig,
        ...edge,
        sourceNode,
        targetNode,
        _sourceAnchor,
        _targetAnchor,
        start,
        end,
        visible: sourceNode?.visible && targetNode?.visible && start && end,
        selected: selectedEdges.includes(id),
        hovering: hoveringEdge?.id === id,
        effecting: allEffectEdges.some((i) => i.id === id),
      };
      arr.push(_edge);
      _edge.visible &&= edgeTypes.includes(_edge.type);
      if (!_edge.visible) return arr;
      // 贝塞尔曲线
      if (_edge.type === 'bezier') {
        _edge.controlPoints = getBezierCurveControlPoints(_sourceAnchor, _targetAnchor);
        _edge.path = getBezierCurvePath(_edge.controlPoints);
      }
      ctx.lineWidth = _edge.lineWidth <= 2 ? _edge.lineWidth * 2 : _edge.lineWidth;
      _edge.isPointIn = ctx.isPointInStroke(_edge.path, gridCoord.x, gridCoord.y);
      ctx.lineWidth = 1;
      return arr;
    }, []);
  }
  // 获取节点连线的映射对象
  get edgesMap() {
    return this._edges.reduce((map, edge) => ((map[edge.id] = edge), map), {});
  }
  // 当前临时节点连线
  get tempEdge() {
    const { tempAnchor, hoveringAnchor } = this;
    if (!tempAnchor || !hoveringAnchor) return null;
    const temp = {
      source: tempAnchor.node.id,
      sourceAnchor: tempAnchor.id,
      target: hoveringAnchor.node.id,
      targetAnchor: hoveringAnchor.id,
    };
    // 是否相同锚点
    temp.isSame = temp.source === temp.target && temp.sourceAnchor === temp.targetAnchor;
    // 是否可添加连线  禁止相同锚点连线
    temp.canAdd = this._edgePrecheck('add', cloneDeep(temp)) !== false && !temp.isSame;
    return temp;
  }
  // 框选矩形
  get regionRect() {
    const { regionAnchor, gridCoord } = this;
    return {
      x: gridCoord.x > regionAnchor.x ? regionAnchor.x : gridCoord.x,
      y: gridCoord.y > regionAnchor.y ? regionAnchor.y : gridCoord.y,
      width: Math.abs(gridCoord.x - regionAnchor.x),
      height: Math.abs(gridCoord.y - regionAnchor.y),
    };
  }
  // 所有图片加载完成
  get allImageLoad() {
    return new Promise((resolve, reject) => {
      const images = this.find('Image');
      if (!images.length) {
        resolve(images);
        return;
      }
      let count = 0;
      for (let item of images) {
        const image = getImage(item.image.src);
        // 监听加载完毕
        image.onload = () => ++count && count === images.length && resolve(images);
        // 图片加载出错
        image.onerror = () => reject(item);
      }
    });
  }
  // 获取节点连线关联的节点分组
  get edgeNodeGroups() {
    const { edges, nodesMap } = this;
    const graph = {}; // 节点之间的关系图
    const visited = new Set(); // 记录已访问的节点
    const groups = []; // 存储节点组的二维数组
    // 深度优先搜索
    const dfs = (nodeId, group) => {
      visited.add(nodeId);
      group.push(nodeId);
      for (const neighbor of graph[nodeId] || []) {
        if (!visited.has(neighbor)) dfs(neighbor, group);
      }
    };
    // 构建关系图（单向图）
    for (const { target, source } of edges) {
      if (!graph[source]) graph[source] = [];
      graph[source].push(target);
    }
    // 遍历所有节点
    for (const nodeId in graph) {
      if (!visited.has(nodeId)) {
        const group = [];
        dfs(nodeId, group);
        groups.push(group);
      }
    }
    // 已连接的节点
    const linkedNodes = groups.flat();
    // 未连接的节点
    const unlinkedNodes = Object.keys(nodesMap).filter((nodeId) => !linkedNodes.includes(nodeId));
    // 添加未连接的节点组
    if (unlinkedNodes.length) groups.push(unlinkedNodes);
    // 返回节点对象组的二维数组
    return groups.map((group) => group.map((nodeId) => nodesMap[nodeId]));
  }
  // 更新画布
  update() {
    // 按照深度属性排序子节点
    this.nodes.sort((a, b) => a.zIndex - b.zIndex);
  }
  // 缩放值计算
  calc(num, { adaptScale = true, adaptPixelRatio = true } = {}) {
    return (num / (adaptScale ? this.scale : 1)) * (adaptPixelRatio ? this.pixelRatio : 1);
  }
  // 根据画布网格坐标获取画布相对坐标
  getOffsetCoord(gridCoord) {
    const { offset, scale } = this;
    return { x: offset.x + gridCoord.x * scale, y: offset.y + gridCoord.y * scale };
  }
  // 根据画布相对坐标获取画布网格坐标
  getGridCoord(offsetCoord) {
    const { offset, scale } = this;
    return { x: (offsetCoord.x - offset.x) / scale, y: (offsetCoord.y - offset.y) / scale };
  }
  // 根据画布相对坐标获取画布绝对坐标
  getAbsoluteCoord(offsetCoord) {
    return { x: offsetCoord.x / this.pixelRatio, y: offsetCoord.y / this.pixelRatio };
  }
  // 根据画布绝对坐标获取相对于浏览器窗口的坐标
  getFixedCoord(absoluteCoord) {
    // 获取画布元素相对视口的坐标
    const offsetXY = getOffsetXY(this.canvas.el);
    return { x: offsetXY.x + absoluteCoord.x, y: offsetXY.y + absoluteCoord.y };
  }
  // 移动
  handleMove({ offsetX = 0, offsetY = 0, e = null }, isDraw = true, force = false) {
    if (!this.draggable && !force) return;
    // 画布处于拖动状态禁止移动
    if (this.dragging) return;
    // 计算偏移量 = 上次偏移量 + 移动偏移量
    this.offset.x = this.lastOffset.x + offsetX;
    this.offset.y = this.lastOffset.y + offsetY;
    // 记录上一次的偏移量
    this.lastOffset.x = this.offset.x;
    this.lastOffset.y = this.offset.y;
    // 有鼠标事件
    if (e) {
      // 触发 hover 事件
      handleHover.call(this);
      // 更新鼠标指针
      updateCursor.call(this, e);
      const { regioning, transformer } = this;
      const { rotating, resizing } = transformer;
      // 框选中状态 触发框选事件
      if (regioning) this.onRegion();
      // 转换器旋转中状态 触发选择器旋转事件
      else if (rotating) transformer.onRotate();
      // 转换器调整大小状态 触发选择器调整大小事件
      else if (resizing) transformer.onResize();
      // 鼠标左键按下 且 有可拖动节点 触发节点拖动事件
      else if (e.buttons === 1 && this.draggableNodes.length) this.onNodeDrag();
    }
    // @ts-ignore 更新文本编辑元素
    this.textareaEl.update();
    this.emit('move', { offsetX, offsetY });
    // 重绘
    if (isDraw) this.draw();
  }
  // 缩放
  handleScale(scale, isDraw = true, force = false) {
    if (!this.scaleable && !force) return;
    this.scale = scale;
    // 缩放范围限制
    if (this.scale > this.maxScale) this.scale = this.maxScale;
    if (this.scale < this.minScale) this.scale = this.minScale;
    // 计算缩放导致的相对偏移量 = （缩放锚点与缩放前偏移量差值） * （1 - 相对缩放比例）
    const offsetLeft = (this.scaleAnchor.x - this.offset.x) * (1 - this.scale / this.lastScale);
    const offsetTop = (this.scaleAnchor.y - this.offset.y) * (1 - this.scale / this.lastScale);
    // 计算缩放后偏移量 = 缩放前偏移量 + 缩放导致的相对偏移量
    this.offset.x += offsetLeft;
    this.offset.y += offsetTop;
    // 记录上一次的偏移量
    if (this.dragging) {
      // 画布处于拖动状态，将缩放导致的相对偏移量同步到上一次偏移量
      this.lastOffset.x += offsetLeft;
      this.lastOffset.y += offsetTop;
    } else {
      this.lastOffset.x = this.offset.x;
      this.lastOffset.y = this.offset.y;
    }
    // 记录上一次的缩放值
    this.lastScale = this.scale;
    // @ts-ignore 更新文本编辑元素
    this.textareaEl.update();
    this.emit('scale', this.scale);
    // 重绘
    if (isDraw) this.draw();
  }
  // 居中画布到目标坐标（目标坐标默认为画布实际绘制区域中心点坐标）
  handleCenter({ x, y } = this.realCenter) {
    const { center: viewCenter } = this.viewport;
    // 强制 将目标坐标移动到画布中心，偏移量：画布视口中心点坐标与居中坐标差值
    this.handleMove({ offsetX: viewCenter.x - x, offsetY: viewCenter.y - y }, true, true);
  }
  // 记录画布历史状态
  handleRecord;
  _handleRecord() {
    const { recordable, history, historyStep } = this;
    if (recordable) {
      const schema = this.toSchema();
      const historyStage = JSON.parse(history[historyStep]);
      const newStage = JSON.parse(schema);
      // 节点 是否有变更
      const nodesDiff = !!diff(historyStage.nodes, newStage.nodes).length;
      // 已选节点 是否有变更
      const selectedNodesDiff = String(newStage.selectedNodes) !== String(historyStage.selectedNodes);
      // 已选节点连线 是否有变更
      const selectedEdgesDiff = String(newStage.selectedEdges) !== String(historyStage.selectedEdges);
      // 是否可深度操作节点状态 是否有变更
      const depthOperationDiff = newStage.depthOperation !== historyStage.depthOperation;
      // 节点连线 是否有变更
      const edgesDiff = !!diff(historyStage.edges, newStage.edges).length;
      if (!(nodesDiff || selectedNodesDiff || selectedEdgesDiff || depthOperationDiff || edgesDiff)) return;
      history.splice(++this.historyStep);
      history.push(schema);
    }
    this.emit('action', this);
  }
  // 设置鼠标指针样式 https://developer.mozilla.org/zh-CN/docs/Web/CSS/cursor
  setCursor(cursor = '') {
    this.canvas.el.style.cursor = cursor;
  }
  // 设置canvas原生title属性
  setTitle(title = '') {
    this.canvas.el.title = title;
  }
  /**
   * Methods
   */
  /**
   * 绘制画布
   */
  draw;
  _draw() {
    // container 元素中 stage 实例非当前 stage 实例，阻断本次绘制
    if (this.containerEl.stage !== this) return;
    const { ctx, nodes, scale, offset, selectedNodes, hoveringNode, regioning, transformer } = this;
    // 清除画布内容
    this.width = this.width;
    // 保存当前状态
    ctx.save();
    // 设置画布变换矩阵 实现 缩放和移动
    ctx.setTransform(scale, 0, 0, scale, offset.x, offset.y);

    // 绘制画布背景
    drawBg.call(this);

    // 绘制节点连线
    drawEdges.call(this);

    // 绘制所有子节点
    nodes.forEach((node) => node.draw(ctx));

    // 绘制已选节点轮廓 及 最顶层父级编组轮廓
    selectedNodes.forEach((node) => (node.drawOutline?.(ctx), node.topGroup?.drawOutline(ctx)));
    // 绘制当前 hover 节点轮廓
    if (!selectedNodes.includes(hoveringNode)) hoveringNode?.drawOutline?.(ctx);

    // 框选状态
    if (regioning) {
      // 绘制框选矩形
      const { regionRect } = this;
      const { r, g, b } = colorToRGBA(Designer.theme);
      draw.fillRect(ctx, { ...regionRect, fillStyle: `rgba(${r}, ${g}, ${b}, 0.19)` });
      draw.rect(ctx, { ...regionRect, lineWidth: this.calc(1) });
    }
    // 非框选状态
    else {
      // 绘制转换器
      transformer.draw(ctx);
      // 绘制辅助线
      drawGuides.call(this);
    }

    // 绘制画布轮廓
    drawOutline.call(this);

    // 恢复被保存的状态
    ctx.restore();

    // 绘制滚动条
    drawScrollbar.call(this);

    this.emit('draw', this);
  }
  /**
   * 添加节点
   * @param {...*} nodes
   */
  add(...nodes) {
    let onlyAdd = false;
    const last = nodes[nodes.length - 1];
    if (getType(last) === 'Boolean') (onlyAdd = last), nodes.pop();
    let nextZIndex = this.nodes.length ? Math.max(...this.nodes.map((i) => i.zIndex)) + 1 : 0;
    nodes.forEach((node) => {
      if (this.nodes.includes(node)) {
        log.warn(node, '节点重复！已忽略添加此节点。');
        return;
      }
      node.parent = this;
      if (getType(node.zIndex) !== 'Number') node.zIndex = nextZIndex++;
      this.nodes.push(node);
    });
    this.update();
    if (onlyAdd) return;
    // 重绘
    this.draw();
    this.handleRecord();
  }
  /**
   * 清理画布
   */
  clear() {
    // 清理数据
    this.nodes = [];
    this.edges = [];
    // 清理画布已选节点及连线
    this.clearSelect();
    // 清理历史状态
    this.clearHistory();
  }
  /**
   * 清理画布已选节点及连线
   */
  clearSelect() {
    this.selectedNodes = [];
    this.selectedEdges = [];
    this.depthOperation = false;
    this.lastTopGroup = null;
    this.editTextNode = null;
    this.transformer.setNodes([]);
    this.emit('select', [], 'clearSelect');
    this.emit('edgeSelect', [], 'clearSelect');
    // 记录画布历史状态
    this._handleRecord();
  }
  /**
   * 设置缩放
   * @param {number} value 缩放值
   * @param {{ x: number, y: number }} scaleAnchor 缩放锚点坐标
   * @param {boolean} isDraw 重绘
   * @param {boolean} force 强制缩放
   */
  setScale(value, scaleAnchor = this.realCenter, isDraw = true, force = false) {
    this.scaleAnchor = scaleAnchor;
    this.handleScale(value, isDraw, force);
  }
  /**
   * 放大（当前缩放值 * 缩放比率）
   * @param {{ x: number, y: number }} scaleAnchor 缩放锚点坐标
   */
  increaseScale(scaleAnchor = this.realCenter) {
    this.setScale(this.scale * this.scaleRatio, scaleAnchor);
  }
  /**
   * 缩小（当前缩放值 / 缩放比率）
   * @param {{ x: number, y: number }} scaleAnchor 缩放锚点坐标
   */
  decreaseScale(scaleAnchor = this.realCenter) {
    this.setScale(this.scale / this.scaleRatio, scaleAnchor);
  }
  /**
   * 原始像素显示
   * @param {boolean} fitPixelRatio 适应像素比
   */
  original(fitPixelRatio = false) {
    const { realCenter, pixelRatio } = this;
    // 缩放比例设置为1
    this.setScale(fitPixelRatio ? pixelRatio : 1, realCenter, false, true);
    // 居中画布
    this.handleCenter(realCenter);
  }
  /**
   * 自适应（将画布绘制区域中心移动到画布中心并设置合适比例全览）
   * @param {number} ratio 最终缩放倍率调整
   */
  adaptive(ratio = 0.9) {
    const { minEnclosingRect, viewport } = this;
    // 不存在画布实际绘制区域，原始像素显示
    if (!minEnclosingRect) {
      this.original();
      return;
    }
    const { width, height, centerX, centerY } = minEnclosingRect; // 最小外接矩形
    const realCenter = this.getOffsetCoord({ x: centerX, y: centerY });
    let finalScale;
    // 最小外接矩形宽高比 > 视口宽高比 => 使用宽比例
    if (width / height > viewport.aspectRatio) finalScale = viewport.width / width;
    // 否则 使用高比例
    else finalScale = viewport.height / height;
    // 最终缩放比例 * 倍率 以留出部分内边距
    this.setScale(finalScale * ratio, realCenter, false, true);
    // 居中画布到 最小外接矩形中心点坐标 以实现全览
    this.handleCenter(realCenter);
  }
  /**
   * 销毁
   */
  destroy() {
    // 销毁 container 元素中的 stage 实例
    this.containerEl && (this.containerEl.stage = undefined);
    // 销毁 designer 元素
    this.designerEl?.remove();
    // 销毁所有绑定事件
    this.abortController.abort();
  }
  /**
   * 寻找节点
   * @param {string | function} selector 选择器 | 过滤函数
   */
  find(selector) {
    // @ts-ignore
    if (getType(selector) === 'Function') return this.allNodes.filter(selector);
    if (getType(selector) === 'String') {
      // @ts-ignore
      const firstChar = selector.slice(0, 1);
      // @ts-ignore
      const afterStr = selector.slice(1, selector.length);
      const key = { '#': 'id', '.': 'name' }[firstChar] || 'className';
      const value = { '#': afterStr, '.': afterStr }[firstChar] || selector;
      return this.allNodes.filter((node) => node[key] === value);
    }
  }
  /**
   * 寻找单个节点
   * @param {string | function} selector 选择器 | 过滤函数
   */
  findOne(selector) {
    return this.find(selector)?.[0] || null;
  }
  /**
   * 设置已选节点
   * @param {any[]} nodes 节点数组
   */
  setSelectedNodes(nodes = []) {
    this.selectedNodes = [...nodes];
    this.emit('select', this.selectedNodes, 'setSelected');
    // 可深度操作节点状态 且 已选节点中有最顶层父级编组不是上一次最顶层父级编组 => 取消可深度操作节点状态
    if (this.depthOperation && nodes.some((node) => node.topGroup !== this.lastTopGroup)) this.depthOperation = false;
    // 将当前可拖动节点更新到转换器
    this.transformer.setNodes(this.draggableNodes);
    this.handleRecord();
  }
  /**
   * 设置已选节点连线
   * @param {any[]} edges 节点连线数组
   */
  setSelectedEdges(edges = []) {
    this.selectedEdges = [...edges];
    this.emit('edgeSelect', this.findEdge(this.selectedEdges), 'setSelected');
    // 重绘
    this.draw();
    this.handleRecord();
  }
  /**
   * 移动当前可拖动节点
   * @param {{x:number,y:number}} offset 移动偏移量
   * @param {boolean} onlyMove 只移动
   */
  moveDraggableNodes(offset = { x: 0, y: 0 }, onlyMove = false) {
    const { draggableNodes, transformer, nodeDragAnchor } = this;
    if (!draggableNodes.length) return;
    draggableNodes.forEach((i) => i.move(offset, true));
    transformer.visible && transformer.updateOffset(offset);
    (nodeDragAnchor.x += offset.x), (nodeDragAnchor.y += offset.y);
    this.emit('nodeMove', draggableNodes);
    if (onlyMove) return;
    // 重绘
    this.draw();
    this.handleRecord();
  }
  /**
   * 设置是否可深度操作节点状态
   * @param {boolean} depthOperation
   */
  setDepthOperation(depthOperation) {
    this.depthOperation = depthOperation;
    // 可深度操作节点状态 将当前可拖动节点更新到转换器
    if (depthOperation) this.transformer.setNodes(this.draggableNodes, false);
    // 重绘
    this.draw();
    this.handleRecord();
  }
  /**
   * 主动触发快捷键行为
   * @param {'undo'|'redo'|'delete'|'selectAll'|'increaseScale'|'decreaseScale'} action
   */
  actionHotkey(action) {
    // 触发快捷键行为
    hotkeyAction.call(this, action);
  }
  /**
   * 快捷键行为预检（需赋值自定义）
   * @param {'undo'|'redo'|'delete'|'selectAll'|'increaseScale'|'decreaseScale'|'copy'|'cut'|'paste'} action 快捷键行为
   * @returns {Promise<boolean> | any}
   */
  hotkeyPrecheck(action, ...args) {}
  /**
   * 节点连线预检（需赋值自定义）
   * @param {'start' | 'add' | 'remove'} mode
   * @param {{id?:string,source:string,sourceNode:*,sourceAnchor:*,target?:string,targetNode?:*,targetAnchor?:*}} edge
   * @returns {boolean | any}
   */
  edgePrecheck(mode, edge) {}
  _edgePrecheck(...args) {
    try {
      const edge = args[1];
      const { allNodesMap } = this;
      const { source, sourceAnchor, target, targetAnchor } = edge;
      edge.sourceNode = allNodesMap[source];
      edge.targetNode = allNodesMap[target];
      return this.edgePrecheck.apply(this, args);
    } catch (error) {
      console.error(error);
    }
  }
  /**
   * 添加节点连线
   * @param {object} edge
   * @param {string} [edge.id] id
   * @param {string} edge.source 起始节点id
   * @param {*} edge.sourceAnchor 起始节点锚点id
   * @param {string} edge.target 目标节点id
   * @param {*} edge.targetAnchor 目标节点锚点id
   * @param {'bezier'} [edge.type] 类型
   * @param {string} [edge.color] 颜色
   * @param {number} [edge.lineWidth] 线宽
   * @param {number[]} [edge.lineDash] 描述虚线模式的线和间隙的交替长度
   * @param {string} [edge.effect] 效果 提供'ant', 'dashed', 'arrows', 'blink'选项进行组合 例：'ant-dashed-arrows-blink': 带有闪烁效果的蚂蚁虚线箭头效果
   */
  addEdge(edge) {
    if (this._edgePrecheck('add', edge) === false) return;
    if (this.findEdge(edge).length) return;
    // 节点连线添加唯一id
    edge.id = uuid();
    this.edges.push(edge);
    this.emit('edgeAdd', edge, this.edges);
    // 重绘
    this.draw();
    this.handleRecord();
  }
  /**
   * 寻找节点连线
   * @param {{id?:string,source?:string,sourceAnchor?:*,target?:string,targetAnchor?:*,type?:'bezier'} | string[]} edge
   */
  findEdge(edge) {
    if (!edge) return [];
    if (Array.isArray(edge)) {
      const { edgesMap } = this;
      return [...new Set(edge)].map((id) => edgesMap[id]).filter(Boolean);
    }
    const { id, source, sourceAnchor, target, targetAnchor, type } = edge;
    return this._edges.filter((i) => {
      let isFilter = true;
      if (isDef(id)) isFilter &&= i.id === id;
      if (isDef(source)) isFilter &&= i.source === source;
      if (isDef(sourceAnchor)) isFilter &&= i.sourceAnchor === sourceAnchor;
      if (isDef(target)) isFilter &&= i.target === target;
      if (isDef(targetAnchor)) isFilter &&= i.targetAnchor === targetAnchor;
      if (isDef(type)) isFilter &&= i.type === type;
      return isFilter;
    });
  }
  /**
   * 移除节点连线
   * @param {{id?:string,source?:string,sourceAnchor?:*,target?:string,targetAnchor?:*,type?:'bezier'} | string[]} edge
   */
  removeEdge(edge, onlyRemove = false) {
    if (!edge) return;
    const removedEdges = this.findEdge(edge);
    if (!removedEdges.length) return;
    if (removedEdges.some((i) => this._edgePrecheck('remove', i) === false)) return;
    this.edges = this.edges.filter((edge) => !removedEdges.some((i) => i.id === edge.id));
    this.emit('edgeRemove', removedEdges, this.edges);
    if (onlyRemove) return;
    // 重绘
    this.draw();
    this.handleRecord();
  }
  /**
   * 设置节点连线
   * @param {string} edgeId 节点连线id
   * @param {*} overrideConfig 覆盖配置
   */
  setEdge(edgeId, overrideConfig) {
    const edge = this.edges.find((i) => i.id === edgeId);
    if (!edge) return;
    Object.assign(edge, overrideConfig, { id: edge.id });
    // 重绘
    this.draw();
    this.handleRecord();
  }
  /**
   * 撤销
   */
  undo() {
    const { history, historyStep } = this;
    if (historyStep === 0) return;
    this.setSchema(history[--this.historyStep], false);
    this.emit('action', this);
  }
  /**
   * 重做
   */
  redo() {
    const { history, historyStep } = this;
    if (historyStep === history.length - 1) return;
    this.setSchema(history[++this.historyStep], false);
    this.emit('action', this);
  }
  /**
   * 清理历史状态
   */
  clearHistory() {
    // 取消防抖函数执行
    this.handleRecord.cancel();
    this.history.splice(0);
    this.history.push(this.toSchema());
    this.historyStep = 0;
  }
  /**
   * 画布数据转 JSON Schema
   */
  toSchema() {
    return Designer.toJSON({
      generateTime: new Date().toLocaleString('zh').replaceAll('/', '-'),
      className: this.className,
      nodes: this.nodes,
      selectedNodes: this.selectedNodes.map((i) => i.id),
      selectedEdges: this.selectedEdges,
      depthOperation: this.depthOperation,
      lastTopGroup: this.lastTopGroup?.id || null,
      edges: this.edges,
      transformer: {
        nodes: this.transformer.nodes.map((i) => i.id),
        points: this.transformer.points,
        rotation: this.transformer.rotation,
      },
    });
  }
  /**
   * JSON Schema 数据还原
   * @param {string|*} schema
   */
  setSchema(schema, isRecord = true) {
    if (getType(schema) === 'String') schema = JSON.parse(schema);
    this.selectedNodes = [];
    this.hoveringNode = null;
    this.depthOperation = false;
    this.lastTopGroup = null;
    this.editTextNode = null;
    // 移除文本编辑元素
    this.textareaEl.remove();
    // 合并画布节点数据
    nodeMerge(this, schema);
    const { selectedNodes, selectedEdges, depthOperation, lastTopGroup, edges, transformer } = schema;
    const { allNodesMap } = this;
    if (getType(selectedNodes) === 'Array') this.selectedNodes = selectedNodes.map((id) => allNodesMap[id]);
    if (getType(selectedEdges) === 'Array') this.selectedEdges = selectedEdges;
    if (getType(depthOperation) === 'Boolean') this.depthOperation = depthOperation;
    if (getType(lastTopGroup) === 'String') this.lastTopGroup = allNodesMap[lastTopGroup];
    if (getType(edges) === 'Array') this.edges = edges;
    // 转换器状态恢复
    if (getType(transformer) === 'Object') {
      this.transformer.recover({ ...transformer, nodes: transformer.nodes.map((id) => allNodesMap[id]) });
    }
    this.emit('select', this.selectedNodes, 'setSchema');
    this.emit('edgeSelect', this.findEdge(this.selectedEdges), 'setSchema');
    // 重绘
    this.draw();
    if (isRecord) this.handleRecord();
  }
  /**
   * 设置/获取 FPS
   * @param {number} fps
   */
  FPS(fps) {
    fps = Number(fps);
    if (!isFinite(fps)) {
      if (!this.draw) this.FPS(144);
      return this.fps;
    }
    if (fps <= 0) fps = 144;
    this.fps = fps;
    // 画布绘制函数节流
    this.draw = throttle(this._draw.bind(this), 1000 / fps);
  }
  /**
   * 设置画布宽高为 container 元素宽高
   * @param {boolean} isDraw 是否重绘
   */
  setContainerSize(isDraw = true) {
    const { containerEl } = this;
    const style = getComputedStyle(containerEl);
    const width = parseFloat(style['width']);
    const height = parseFloat(style['height']);
    if (width === containerEl.width && height === containerEl.height) return;
    containerEl.width = width;
    containerEl.height = height;
    this.width = Math.ceil(width);
    this.height = Math.ceil(height);
    // 重绘
    if (isDraw) this._draw();
  }
  /**
   * 更新画布像素比
   * @param {number} pixelRatio 当前显示设备的物理像素分辨率与 CSS 像素分辨率之比
   */
  updatePixelRatio(pixelRatio = window.devicePixelRatio) {
    // 更新画布像素比
    this.canvas.updatePixelRatio(pixelRatio);
    // 根据 container 元素自动设置宽高
    if (this.autoSize) this.setContainerSize(false);
    // 重绘
    this.draw();
  }
  /**
   * 设置画布网格
   * @param {boolean|gridConfig} config
   */
  setGrid(config) {
    if (!config) this.grid = false;
    else if (config === true) this.grid = gridConfig;
    else if (getType(config) === 'Object') this.grid = { ...gridConfig, ...config };
    if (!this.grid) return;
    // @ts-ignore
    const { size, type, color, radius } = this.grid;
    const patternCanvas = new Canvas({ width: size, height: size, pixelRatio: 1 });
    const patternCtx = patternCanvas.getContext();
    if (type === 'dot') {
      patternCtx.fillStyle = color;
      patternCtx.arc(size / 2, size / 2, radius, 0, 2 * Math.PI, false);
      patternCtx.fill();
    }
    if (type === 'line') {
      patternCtx.strokeStyle = color;
      patternCtx.lineWidth = 0.5;
      patternCtx.rect(0, 0, size, size);
      patternCtx.stroke();
    }
    Stage.gridPattern = patternCanvas.el;
    // 重绘
    this.draw?.();
  }
  /**
   * 设置节点连线默认配置
   * @param {edgeConfig} config
   */
  setEdgeConfig(config) {
    this.edgeConfig = { ...edgeConfig, ...config };
  }
  /**
   * 画布数据保存
   */
  save() {
    // 更新转换器
    this.transformer.setNodes(this.draggableNodes);
    // 记录画布历史状态
    this._handleRecord();
  }
  /**
   * 画布配置更新
   * @param {*} config
   */
  setConfig(config) {
    Object.assign(this, config);
    // 更新子节点
    if (config.nodes) this.update();
    // 画布数据保存
    this.save();
  }
  /**
   * 画布转换为 Blob 对象
   * @param {function} callback 回调函数
   * @param {string} type 指定图片格式
   * @param {number} quality 图片质量
   */
  toBlob(callback, type = 'image/png', quality = 1) {
    return new Promise((resolve, reject) => {
      try {
        this.ctx.canvas.toBlob((blob) => (callback?.(blob), resolve(blob)), type, quality);
      } catch (error) {
        reject(error);
      }
    });
  }
  /**
   * 画布转换为 data URI
   * @param {string} type 指定图片格式
   * @param {number} quality 图片质量
   */
  toDataURL(type = 'image/png', quality = 1) {
    return this.ctx.canvas.toDataURL(type, quality);
  }
}

// 全局离屏 canvas 对象
Stage.canvas = new Canvas();
// 网格图案
Stage.gridPattern = undefined;
